Skip to content

fix(runtime): support CSS v-bind() on Lynx native - #144

Open
KealanAU wants to merge 20 commits into
Huxpro:mainfrom
KealanAU:fix/opacity-investigation
Open

fix(runtime): support CSS v-bind() on Lynx native#144
KealanAU wants to merge 20 commits into
Huxpro:mainfrom
KealanAU:fix/opacity-investigation

Conversation

@KealanAU

@KealanAU KealanAU commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds Background-Thread-safe support for v-bind() in Vue <style> blocks on Lynx native.

Compatibility

  • Requires Lynx engine ≥ 3.9.0 — the first tagged release containing the #5912 propagation code (changed_css_vars, MarkCustomPropertiesDirty, RecursivelyMarkChildrenCSSVariableDirty in FiberSetInlineStyles, closing lynx#5889). The 3.8.1 tag lacks it and no later 3.8.x tag exists. On earlier engines the vars are stamped on the root but never reach descendants. Stated in the EN + ZH compatibility docs and the changeset.
  • Requires enableCSSInlineVariables: true. enableCSSInheritance is not required — the css-features example now sets it to false so inheritance mode cannot mask whether #5912 propagation works on its own.
  • Depends on chore: update Lynx toolchain to 3.9 release line #192. This branch will be refreshed and CI rerun once that toolchain change lands; the toolchain must ship the ≥ 3.9.0 engine this feature requires. (Cross-fork PRs can't retarget the base branch, so this is tracked as an explicit dependency rather than a GitHub stack.)

Verification

  • pnpm test:local in packages/upstream-tests — verifies the BG runtime emits root-only SET_STYLE ops (mount, reactive update, fragments, v-show). It does not verify native descendant cascade; that is engine behavior.
  • Pending: native simulator re-verification on engine 3.9.0 with enableCSSInlineVariables: true and enableCSSInheritance: false, confirming a descendant consuming var(...) updates reactively. The videos below were recorded with enableCSSInheritance: true and will be replaced.
Before After
CleanShot.2026-04-05.at.15.29.59.mp4
CleanShot.2026-04-05.at.15.07.14.mp4

KealanAU and others added 12 commits April 4, 2026 18:48
- Move use-css-vars.md and LYNX-ISSUES.md from runtime/src/ to
  packages/vue-lynx/docs/ so rslib does not try to bundle them
- Restore root package.json private: true (was accidentally set to false)
…seCssVars

Two critical fixes:

1. Add onBeforeUpdate + onMounted wrapping (matching upstream Vue):
   - patchProp's SET_STYLE overwrites CSS vars on every re-render since
     it doesn't know about them. Without onBeforeUpdate, CSS vars silently
     vanish whenever a style binding changes but the CSS var value doesn't.
   - VNode tree changes (v-if, v-for) also need CSS vars re-applied to
     newly created root elements.

2. Restrict SF_ARRAY_CHILDREN walking to Fragment vnodes only:
   - The standalone `if (shapeFlag & SF_ARRAY_CHILDREN)` also fired for
     element vnodes with children, applying CSS vars to every descendant
     element (O(N) SET_STYLE ops). Root elements are sufficient since
     enableCSSInheritance cascades vars to descendants.
- Move v-bind() from "Upcoming" to "Works (requires config)"
- Add required config snippet (enableCSSInlineVariables + enableCSSInheritance)
- Document the known layout-property limitation
- Remove the workaround snippet (no longer needed)
- Update both EN and ZH versions
Revert element-registry.ts and TransitionGroup.ts type annotation
changes that were rejected in PR Huxpro#122 review. Add changeset for
the v-bind() CSS vars feature.
@vercel

vercel Bot commented Apr 5, 2026

Copy link
Copy Markdown

@KealanAU is attempting to deploy a commit to the huxpro's projects Team on Vercel.

A member of the Team first needs to authorize it.

@KealanAU
KealanAU marked this pull request as ready for review April 5, 2026 05:33
@vercel

vercel Bot commented Apr 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vue-lynx Ready Ready Preview, Comment Apr 5, 2026 0:03am

@Huxpro

Huxpro commented Apr 5, 2026

Copy link
Copy Markdown
Owner

@KealanAU Thanks for the thorough investigation and the detailed PR description -- the root cause analysis of the Lynx engine's CSS var inheritance gap is spot on, and the isolation test (CSSInheritanceTest.vue) is a great addition.

I've been digging into this and wanted to share where I've landed.

What we've verified

I traced through the Lynx engine source and confirmed the bug. The root cause is that FiberSetInlineStyles calls UpdateCSSInlineVariables to store --* properties, but never calls RecursivelyMarkChildrenCSSVariableDirty afterward -- so descendants never see the change. The working path (FiberElement::UpdateCSSVariable, used by setProperty) does all three steps: MarkCustomPropertiesDirty, MarkStyleDirty, and RecursivelyMarkChildrenCSSVariableDirty. I've updated the upstream issue with the full analysis and suggested fix: lynx-family/lynx#5889

I also verified on Lynx Explorer 3.8 that same-element CSS vars (set and consumed on the same element via inline :style) work, but parent-to-child inheritance does not. This confirms the workaround is necessary on current engine versions.

Separate fix for entry.ts

The entry.ts change (from encodeData.compilerOptions to encodeData.sourceContent.config) is actually a pre-existing bug from #127 -- the CSS config options were being injected into the wrong section and never reaching the engine. I've split this out into a standalone fix: #145.

Performance concerns with stampElementDescendants

The workaround stamps CSS vars on every element in the component subtree, which means:

  • O(N) SET_STYLE ops per CSS var change, where N = total elements in the subtree (instead of O(1) for root-only stamping)
  • O(N) on every component re-render (not just when CSS vars change), because onBeforeUpdate re-stamps the full subtree to compensate for patchProp overwriting the merged styles
  • Cross-component double-stamping: if a parent and child component both use v-bind() in CSS, the parent's walk stamps into the child's subtree, then the child's own useCssVars stamps them again

For small components this is fine, but for components with 100+ elements or frequent updates, this could become a bottleneck.

Questions

Before merging this, I'd like to understand:

  1. How urgent is this for you? If it's not blocking, I'd prefer to first see how hard the upstream fix is (the suggested change in lynx#5889 is fairly contained -- adding ~5 lines after the ForEachLepusValue loop). With the engine fix, we'd get O(1) root-only stamping with no workaround needed.

  2. If we do merge the workaround, I think we should document the performance characteristics so users know to prefer inline :style for performance-critical reactive values in large components.

Thanks again for all the work here -- the examples, tests, and the debugging that led to finding the entry.ts config bug are all valuable.

@KealanAU

KealanAU commented Apr 5, 2026

Copy link
Copy Markdown
Contributor Author

@Huxpro I totally agree that O(N) is terrible to pay on every change. I'm happy to wait for
the upstream engine fix instead, then update or close this PR. Since you've narrowed it
down to the lines and functions in lynx#5889, the O(1) solution is definitely the long-term
solution — we don't want a short-term fix that ends up sticking around long-term.

I did think it was still an upstream issue that would need to be resolved, and had made the
PR since it would be easy to pull out, but this isn't urgent — just wanting to help get
Vue working well on Lynx and learn.

Thanks for splitting it off into its own PR for the other fix and the deeper dive. I was
going to try to find the function and line in Lynx causing the problem today!

@Huxpro

Huxpro commented Apr 6, 2026

Copy link
Copy Markdown
Owner

@KealanAU I just confirmed that we can make it work by fixing lynx#5889! I'll see what the nearest release I can land it in is. Thanks for the detailed issue analysis and the comprehensive CSS feature tests! I'll get back to you when it's landed.

image

@Huxpro Huxpro self-assigned this Apr 6, 2026
Resolves conflict in entry.ts: both sides had the same fix
(sourceContent.config instead of compilerOptions). Also includes
the stampElementDescendants workaround removal and root-only
stamping tests from the current session.
@KealanAU
KealanAU force-pushed the fix/opacity-investigation branch from ebb4ef3 to e80ced0 Compare April 9, 2026 16:38
@Huxpro

Huxpro commented Apr 16, 2026

Copy link
Copy Markdown
Owner

The engine fix is merged lynx-family/lynx#5912 and will be released in 3.8 (May)

@KealanAU

Copy link
Copy Markdown
Contributor Author

@Huxpro , Perfect, I'll remove the work around then and we can merge in May

@KealanAU KealanAU changed the title fix(runtime): work around Lynx CSS var inheritance gap in useCssVars fix(runtime): support CSS v-bind() on Lynx native Jun 25, 2026

@Huxpro Huxpro left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking from my side: this adds user-facing v-bind() support/docs that appear to depend on the newer Lynx engine/toolchain surface, but the compatibility baseline is still ambiguous here.

If this lands before #192, readers can reasonably take the docs as applying to the default current toolchain, while the required support may only exist on the newer release line. I think this either needs to be explicitly stacked after #192 or the docs need to state the minimum required Lynx engine/toolchain version very clearly.

Also, the linked Lynx issue references should be reconciled. I saw both lynx#5912 and lynx#5889 in the surrounding discussion/context, and that kind of mismatch makes the support story harder to trust.

…ersion

Standardize all references on the fix PR (#5912, closing issue #5889) and
document that v-bind() in CSS requires Lynx engine >= 3.8.1.
@KealanAU

KealanAU commented Jun 27, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — both addressed.

Issue references: the two numbers are an issue↔fix pair, not a mismatch. lynx-family/lynx#5912 is the fix PR; it closes issue #5889. Every mention in the code comments, the spec, the changeset, and the docs now reads "#5912 (closing #5889)" consistently.

Compatibility baseline: you're right that this depends on the engine fix in #5912, which ships in the 3.8.1 line. I've made the requirement explicit:

@Huxpro Huxpro left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rewrite to root-only stamping plus engine propagation is the right long-term approach, and dropping the O(N) descendant workaround and the enableCSSInheritance requirement is a clear improvement — the new root-only tests capture the contract well. The #5912/#5889 references and the "requires engine ≥ 3.8.1" baseline now read consistently across the code, changeset, and EN/ZH docs, which resolves the earlier ambiguity.

The remaining blocker is ordering: this should land after the 3.8.1 toolchain bump in #192, which is currently still open and showing merge conflicts. Holding for #192 to merge (and to clear the prior change request) before this goes in.


Generated by Claude Code

@Huxpro Huxpro left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rechecked the current head against the tagged Lynx engine sources, and the stated minimum version is still a release blocker:

Please address these points before approval:

  1. Change the compatibility docs, changeset, and PR description to require Lynx engine >= 3.9.0, unless there is a specific tagged 3.8.x backport that contains the same propagation code.
  2. Set enableCSSInheritance: false in the css-features example used to validate this feature. It is currently true, so the example and CSSInheritanceTest.vue can mask whether #5912 propagation works without inheritance mode.
  3. Re-run the native reactive-update verification on the documented minimum engine with enableCSSInlineVariables: true and enableCSSInheritance: false, confirming a descendant consuming var(...) updates. The local use-css-vars.spec.ts coverage only proves that the BG runtime emits root SET_STYLE ops; it cannot prove the native engine cascades or invalidates descendant styles.
  4. Update the root-only test comments so they do not present native propagation as something that test itself verifies.

This PR also depends on #192, so its branch will need to be refreshed after that toolchain change lands and CI rerun on the resulting head.

KealanAU added 2 commits July 17, 2026 21:12
The #5912 propagation code (changed_css_vars, MarkCustomPropertiesDirty,
RecursivelyMarkChildrenCSSVariableDirty) first appears in the 3.9.0 tag;
3.8.1 lacks it and no later 3.8.x tag exists.

- Bump minimum engine to 3.9.0 in EN/ZH docs, changeset, and code comments
- Set enableCSSInheritance: false in css-features example so it cannot mask
  engine-level custom property propagation
- Reword root-only spec comments: they verify BG op emission only, not
  native descendant cascade
@KealanAU

KealanAU commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for catching the version gap. All four points are addressed on the latest head.

  • I checked the tagged sources and agree: FiberSetInlineStyles in 3.8.1 contains none of the propagation code, and no 3.8.x tag exists after 3.8.1, so 3.9.0 is the correct minimum.
  • The EN/ZH docs, the changeset, the code comments, and the PR description now require Lynx engine >= 3.9.0.
  • The css-features example now sets enableCSSInheritance: false, with a comment noting it must stay off, and CSSInheritanceTest.vue is relabelled as an engine-propagation test.
  • The root-only comments in use-css-vars.spec.ts now say those tests verify only the BG-runtime contract (root stamped, no redundant descendant SET_STYLE), not native cascade.
  • I re-ran native verification with enableCSSInlineVariables: true and enableCSSInheritance: false on both sides of the boundary, using the same bundle on LynxExplorer 3.8.1 and 3.9.0. On 3.8.1 the descendants consuming var(...) never receive the color; on 3.9.0 they render and update reactively at depth 1 and 2. The recording below shows the 3.9.0 run; I reproduced the failure locally on 3.8.1 with the same bundle.
CleanShot.2026-07-17.at.21.47.45.mp4

I also merged latest main into the branch and all suites pass. Two notes for follow-up: #192 currently targets the 3.8.1 toolchain line, which is below the new minimum, so it needs to move to 3.9.0 before the final refresh and CI rerun. Separately, the lynxjs.org quick-start currently serves the 3.8.1 Explorer download, so developers following it will land one version below the minimum; the docs could point at the GitHub 3.9.0 release instead.
lynx-family/lynx-website#1185
lynx-family/lynx-website#1186

@Huxpro

Huxpro commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Status update (2026-08-17): the implementation branch is still textually mergeable, and the author’s latest comment supplies the requested 3.9.0 native verification with enableCSSInlineVariables=true and enableCSSInheritance=false. However this remains not ready to merge:\n\n1. The formal review decision is still CHANGES_REQUESTED.\n2. This PR explicitly depends on #192; #192 has now been refreshed onto current main but cannot merge until the maintainer-specified Vapor 0.6.0 release order is satisfied.\n3. After #192 lands, this branch must be refreshed onto that resulting main, CI rerun, and the reviewer must re-review/dismiss the stale change request on the refreshed head.\n\nNo code change is appropriate on this branch before #192 lands; doing so would validate against the wrong toolchain baseline.

@Huxpro

Huxpro commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Superseded by #378. The original cross-fork head is read-only to the maintainer account, so #378 contains the semantically rebased continuation on current main, the conflict fixes, focused regression verification, and fresh CI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants